#include <library.h>

#define DEFAULT_PANS ([ \
      "left" : ([ ]), \
      "middle" : ([ \
         "very small" : ({ "1/9", 1 }), \
         "small" : ({ "1/3", 3 }), \
         "medium" : ({ "1", 9 }), \
         "large" : ({ "3", 27 }), \
         "very large" : ({ "9", 81 }) ]), \
      "right" : ([ ]) ])

inherit "/std/object";

private nosave int balance;
private nosave string *doing;
private nosave mapping pans;

int move_a_weight( string weight_size, string pan_from, string pan_to );

void setup() {
   set_name( "balance" );
   set_short( "weighing balance" );
   add_adjective( "weighing" );
   add_alias( ({ "pan", "pans", "weight", "weights" }) );
   set_read_mess( "Thys balanss ackurayte to one-nyneth of ae pounde." );
   set_weight( 200 );
   reset_get();
   doing = ({ });
   pans = DEFAULT_PANS;
} /* setup() */

string *query_doing() { return doing; }

mapping query_pans() { return pans; }

string pans_look() {
   int i;
   int j;
   string pans_status;
   string *places;
   string *weights;

   pans_status = "";
   places = m_indices( pans );
   for ( i = 0; i < sizeof( places ); i++ ) {
      pans_status += "The "+ places[ i ] +" pan ";
      if ( !m_sizeof( pans[ places[ i ] ] ) )
         pans_status += "is empty.\n";
      else {
         weights = m_indices( pans[ places[ i ] ] );
         for ( j = 0; j < sizeof( weights ); j++ )
            weights[ j ] = add_a( weights[ j ] ) +" weight";
         pans_status += "holds "+ query_multiple_short( weights ) +".\n";
      }
   }
   switch ( balance ) {
      case -1 :
         pans_status += "The left pan hangs lower than the right pan.\n";
         break;
      case 0 :
         pans_status += "The left pan hangs level with the right pan.\n";
         break;
      case 1 :
         pans_status += "The left pan hangs higher than the right pan.\n";
         break;
   }
   return pans_status;
} /* pans_look() */

void recalculate_balance() {
   int i;
   int j;
   int old_balance;
   string *places;
   string *weights;
   mapping pan_weights;

   pan_weights = ([ ]);
   places = m_indices( pans );
   for ( i = 0; i < sizeof( places ); i++ ) {
      pan_weights[ places[ i ] ] = 0;
      if ( !m_sizeof( pans[ places[ i ] ] ) ) {
         continue;
      }
      weights = m_indices( pans[ places[ i ] ] );
      for ( j = 0; j < sizeof( weights ); j++ ) {
         pan_weights[ places[ i ] ] +=
               pans[ places[ i ] ][ weights[ j ] ][ 1 ];
      }
   }
   old_balance = balance;
   if ( pan_weights[ "left" ] > pan_weights[ "right" ] ) {
      balance = -1;
   } else {
      if ( pan_weights[ "left" ] == pan_weights[ "right" ] ) {
         balance = 0;
      } else {
         balance = 1;
      }
   }
   if ( old_balance == balance ) {
      switch ( balance ) {
         case -1 :
            tell_room( environment(), "The arm rocks a bit but steadies, "+
                  "with the left pan still hanging lowest.\n" );
            break;
         case 1 :
            tell_room( environment(), "The arm rocks a bit but steadies, "+
                  "with the right pan still hanging lowest.\n" );
            break;
      }
      return;
   }
   switch ( balance ) {
      case -1 :
         tell_room( environment(), "The arm of the balance tips and the "+
               "left pan ends up hanging lowest.\n" );
         break;
      case 0 :
         tell_room( environment(), "The arm of the balance levels out, with "+
               "the left and right pans hanging level.\n" );
         break;
      case 1 :
         tell_room( environment(), "The arm of the balance tips and the "+
               "right pan ends up hanging lowest.\n" );
         break;
   }
} /* recalculate_balance() */

int reset_weights() {
   pans = DEFAULT_PANS;
   if (sizeof(doing)) {
      pans[ "left" ][ doing[ 1 ] ] = ({ "?", 1 + random( 121 ) });
   }
   add_succeeded_mess("$N $V the weights on $D.\n");
   recalculate_balance();
   return 1;
} /* reset_weights() */

void init() {
   add_command("weigh", "<indirect:object:me'thing(s)'> on <direct:object>",
               (:this_object()->weigh_something($1):));
   add_command("reset", "weights on balance",
               (: reset_weights() :));
   add_command("figure", "", (:this_object()->figure_it_out():));
   add_command("move",
               "<string'size'> weight from <string'position'> pan to <string'position'> pan",
               (:this_object()->move_a_weight($4[0], $4[1], $4[2]):));
   add_command("move",
               "<string'size'> weight to <string'position'> pan",
               (:this_object()->move_a_specific_weight($4[0], $4[1]):));
} /* init() */

string weight_string( int weight ) {
   int wholes;
   int ninths;

   wholes = weight / 9;
   ninths = weight % 9;
   if ( wholes && ninths ) {
      return wholes +" "+ ninths +"/9 lb";
   }
   if ( wholes ) {
      return wholes +" lb";
   }
   return ninths +"/9 lb";
} /* weight_string() */

int weigh_something( object* obs ) {
   int i;
   int info;
   int weight;
   object person;

   if ( sizeof( doing ) ) {
      person = find_player( doing[ 0 ] );
      if ( person == this_player() ) {
         add_failed_mess( "Hold your horses, you're trying to get the hang of it "+
               "still.\n" );
         return 0;
      }
      if ( person && ( environment( person ) == environment() ) ) {
         add_failed_mess( (string)person->one_short() +" is using "+
               "the balance at the moment.  Come back when "+
               (string)person->query_pronoun() +" has finished.\n" );
         return 0;
      }
      doing = ({ });
      pans = DEFAULT_PANS;
   }
   info = (int)LIBRARY->query_player_quest_info(
      (string)this_player()->query_name(), "balance" );
   switch ( info ) {
      case 0 :
         add_failed_mess( "You don't know how the balance works to weigh anything.\n"+
               "Try \"look\"ing at the balance, the pans and the weights, "+
               "and then maybe you can \"figure\" it out.\n" );
         return 0;
      case 1 :
         add_failed_mess( "You're still not too sure how the balance works to weigh "+
               "anything.\nTry \"look\"ing at the balance, the pans and the "+
               "weights, and then maybe you can \"figure\" it out.\n" );
         return 0;
      case 2 :
         add_failed_mess( "You're very nearly sure how the balance works, but maybe "+
               "you should try to \"figure\" it out once more before you "+
               "weigh anything.\n" );
         return 0;
   }
   for ( i = 0; i < sizeof( obs ); i++ ) {
      weight = obs[ i ]->query_complete_weight();
      if ( !weight ) {
         write( obs[ i ]->the_short() + " doesn't weigh anything.\n" );
         continue;
      }
      if ( weight > 121 ) {
         write( obs[ i ]->the_short() +
               " is heavier than all the weights available put together.\n" );
         continue;
      }
      write( obs[ i ]->the_short() +
            " weighs "+ weight_string( weight ) +".\n" );
   }
   add_succeeded_mess(({ "", "$N $V $I on $D.\n" }), obs);
   return 1;
} /* weigh_something() */

string long( string words, int dark ) {
   int i, j;
   string long;
   string *bits;
   string *places;
   string *weights;

   if (!words) {
      words = "balance";
   }
   bits = explode( words, " " );
   switch ( bits[ sizeof( bits ) - 1 ] ) {
      case "balance" :
         return "This is a largish bronze balance, securely bolted in "+
               "place.  The main part of the balance is a long arm which "+
               "pivots at its centre.  There is a pan hanging from each end "+
               "of the arm such that it will be level when the weights in "+
               "the pans are equal.  A third pan is fixed to a stationary "+
               "part of the balance where the weights can be held when not "+
               "in use.\n"+ pans_look() +"You could probably use the balance "+
               "to \"weigh\" something.\nThere appears to be something "+
               "written on it.\n";
      case "pan" :
      case "pans" :
         return "There are three pans.  One pan hangs from the left end of "+
               "arm, one from the right end and there is a third pan in the "+
               "middle.\n"+ pans_look();
      case "weight" :
      case "weights" :
         long = "There are weights of many different sizes in the pans.  "+
               "They are:\n";
         bits = ({ });
         places = m_indices( pans );
         for ( i = 0; i < sizeof( places ); i++ ) {
            if ( !m_sizeof( pans[ places[ i ] ] ) ) {
               continue;
            }
            weights = m_indices( pans[ places[ i ] ] );
            for ( j = 0; j < sizeof( weights ); j++ ) {
               if ( sizeof( doing ) ) {
                  if ( doing[ 1 ] == weights[ j ] ) {
                     continue;
                  }
               }
               bits += ({ add_a( weights[ j ] ) +" weight marked with \""+
                     pans[ places[ i ] ][ weights[ j ] ][ 0 ] +" lb\"" });
            }
         }
         long += "      " + implode( bits[ 0 .. sizeof( bits ) - 2 ],
               ",\n      " ) +",\n  and "+ bits[ sizeof( bits ) - 1 ] +".\n";
         long += "The weights can be \"move\"d from one pan to another and "
                 "\"reset\" back to their starting positions.\n";
         return long;
   }
   return "You're not quite sure what you're looking at.\n";
} /* long() */

int figure_it_out() {
   int info;
   object person;

   if ( sizeof( doing ) ) {
      person = find_player( doing[ 0 ] );
      if ( person == this_player() ) {
         notify_fail( "You're already engaged in figuring out how the "+
               "balance can be used to weigh something.\n" );
         return 0;
      }
      if ( person && ( environment( person ) == environment() ) ) {
         notify_fail( (string)person->one_short() +" is using "+
               "the balance at the moment.  Come back when "+
               (string)person->query_pronoun() +" has finished.\n" );
         return 0;
      }
      doing = ({ });
      pans = DEFAULT_PANS;
   }
   info = (int)LIBRARY->query_player_quest_info(
      (string)this_player()->query_name(), "balance" );
   if ( info > 2 ) {
      write( "You already know how the balance works.\n" );
      return 1;
   }
   doing = ({ (string)this_player()->query_name(), ({ "red", "green",
         "blue" })[ info ] });
   write( "You see "+ add_a( doing[ 1 ] ) +" weight in the middle pan "+
         "that you hadn't noticed before.  Maybe you could use this to "+
         "experiment, so you place it in the left pan.\n" );
   say( (string)this_player()->one_short() +" moves "+
         add_a( doing[ 1 ] ) +" weight from the middle pan to the left "+
         "pan.\n" );
   pans[ "left" ][ doing[ 1 ] ] = ({ "?", 1 + random( 121 ) });
   recalculate_balance();
   return 1;
} /* figure_it_out() */

int move_a_specific_weight( string weight_size, string pan_to ) {
   object person;
   string pan_from;

   if ( sizeof( doing ) ) {
      person = find_player( doing[ 0 ] );
      if ( !person ) {
         doing = ({ });
         pans = DEFAULT_PANS;
      } else {
         if ( person != this_player() ) {
            if ( environment( person ) == environment() ) {
               add_failed_mess( (string)person->one_short() +" is "+
                     "using the balance at the moment.  Come back when "+
                     (string)person->query_pronoun() +" has finished.\n" );
               return 0;
            } else {
               doing = ({ });
               pans = DEFAULT_PANS;
            }
         }
      }
   }

   if ( !pans[ pan_to ] ) {
      add_failed_mess( "There is a left pan, a middle pan and a right pan, but "+
            "no "+ pan_to +" pan.\n" );
      return 0;
   }
   foreach (pan_from in keys(pans)) {
      if ( pans[ pan_from ][ weight_size ] ) {
         return move_a_weight(weight_size, pan_from, pan_to);
      }
   }
   add_failed_mess("Unable to find the " + weight_size + " weight.\n");
   return 0;
} /* move_a_specific_weight() */

int move_a_weight( string weight_size, string pan_from, string pan_to ) {
   object person;

   if ( sizeof( doing ) ) {
      person = find_player( doing[ 0 ] );
      if ( !person ) {
         doing = ({ });
         pans = DEFAULT_PANS;
      } else {
         if ( person != this_player() ) {
            if ( environment( person ) == environment() ) {
               add_failed_mess( (string)person->one_short() +" is "+
                     "using the balance at the moment.  Come back when "+
                     (string)person->query_pronoun() +" has finished.\n" );
               return 0;
            } else {
               doing = ({ });
               pans = DEFAULT_PANS;
            }
         }
      }
   }
   if ( !pans[ pan_from ] ) {
      add_failed_mess( "There is a left pan, a middle pan and a right pan, but "+
            "no "+ pan_from +" pan.\n" );
      return 0;
   }
   if ( !pans[ pan_to ] ) {
      add_failed_mess( "There is a left pan, a middle pan and a right pan, but "+
            "no "+ pan_to +" pan.\n" );
      return 0;
   }
   if ( !pans[ pan_from ][ weight_size ] ) {
      add_failed_mess( "There isn't "+ add_a( weight_size ) +" weight in the "+
            pan_from +" pan.\n" );
      return 0;
   }
   if ( pan_from == pan_to ) {
      add_failed_mess( "The "+ weight_size +" weight is already in the "+
            pan_to +" pan.\n" );
      return 0;
   }
   if ( sizeof( doing ) ) {
      if ( weight_size == doing[ 1 ] ) {
         notify_fail( "You don't feel like moving the "+ doing[ 1 ] +
               " weight since that's what you're trying to weigh.\n" );
         return 0;
      }
   }
   pans[ pan_to ][ weight_size ] = pans[ pan_from ][ weight_size ];
   map_delete( pans[ pan_from ], weight_size );
   write( "You move the "+ weight_size +" weight from the "+ pan_from +
         " pan to the "+ pan_to +" pan.\n" );
   say( (string)this_player()->one_short() +" moves "+
         add_a( weight_size ) +" weight from the "+ pan_from + " pan of "+
         "the balance to the "+ pan_to +" pan.\n" );
   recalculate_balance();
   if ( sizeof( doing ) && !balance ) {
      call_out( "it_is_balanced", 0, this_player() );
   }
   return 1;
} /* move_a_weight() */

void it_is_balanced( object person ) {
   int info;

   tell_object( person, "You feel a small surge of self-esteem to have found "+
         "that the "+ doing[ 1 ] +" weight weighs "+
         weight_string( pans[ "left" ][ doing[ 1 ] ][ 1 ] ) +".\n" );
   info = (int)LIBRARY->query_player_quest_info( (string)person->query_name(),
         "balance" );
   info++;
   switch ( info ) {
      case 1 :
         tell_object( person, "You've made a good start at working out "+
               "how the balance operates.  You think you should practice "+
               "with it a couple of times more to get the hang of it, "+
               "though.\n" );
         person->adjust_xp( 5000 );
         break;
      case 2 :
         tell_object( person, "You're definitely getting to understand how "+
               "the balance operates.  You think you should practice with "+
               "it once more to get used to it completely, though.\n" );
         person->adjust_xp( 10000 );
         break;
      case 3 :
         tell_object( person, "You're now adept at using the balance and "+
               "can use it to weigh anything.\n" );
/* Quest!
 * Name: balance quest
 * Title: Expert Balancer
 * Story: discovered how to weigh things using powers of three
 * Level: 3
 */
         if ( interactive( person ) ) {
            if ( !LIBRARY->query_quest_done( (string)person->query_name(),
                  "balance quest" ) ) {
               LIBRARY->set_quest( (string)person->query_name(),
                     "balance quest" );
            }
         }
         break;
      case 4:
         info--;
         break; //if people want to keep moving the weights, let them.
      default :
         tell_object( person, "Something has gone wrong with the balance.  "+
               "Please contact Wodan about it.\n" );
   }
   LIBRARY->set_player_quest_info( (string)person->query_name(), "balance",
         info );
   tell_object( person, "You put all the weights back into the middle "+
         "pan.\n" );
   tell_room( environment(), (string)person->the_short() +
         " seems satisfied with "+ (string)person->query_objective() +"self, "+
         "and returns all of the weights to the middle pan.\n", person );
   doing = ({ });
   pans = DEFAULT_PANS;
} /* it_is_balanced() */
